﻿#region COPYRIGHT© 2005-2013 Phillip Clark. All rights reserved.

// For licensing information see License.txt (MIT style licensing).

#endregion

namespace FlitBit.Parse
{
	/// <summary>
	///   Specifies a character or sequence to match.
	/// </summary>
	public abstract partial class Match
	{
		/// <summary>
		///   Indicates the kind of match specified by a <see cref="Match" />
		/// </summary>
		public enum MatchKind
		{
			/// <summary>
			///   Indicates input must match the exact sequence.
			/// </summary>
			Exact = 0,

			/// <summary>
			///   Indicates input must match any of the characters in the sequence.
			/// </summary>
			Any = 1,

			/// <summary>
			///   Indicates the input must match NONE of the characters in the sequence.
			/// </summary>
			None = 2,

			/// <summary>
			///   Indicates the input must match all of the characters in the sequence.
			/// </summary>
			All = 4
		}

		/// <summary>
		///   The EOF match; used as a marker.
		/// </summary>
		public static readonly Match Eof = new MatchEndOfInput();

		/// <summary>
		///   The NULL match; used as a marker.
		/// </summary>
		public static readonly Match NullMatch = CharRule.MatchExact('\u0000');

		static readonly int CHashCodeSeed = typeof(Match).AssemblyQualifiedName.GetHashCode();

		readonly MatchKind _kind;

		internal Match(MatchKind kind)
		{
			_kind = kind;
		}

		internal MatchKind Kind { get { return _kind; } }

		/// <summary>
		///   Determines if the cursor position within the input is at a matching sequence.
		/// </summary>
		/// <param name="input">the input</param>
		/// <param name="cursor">the cursor</param>
		/// <returns>
		///   <em>true</em> if positioned at a matching sequence; otherwise <em>false</em>.
		/// </returns>
		public abstract bool At(char[] input, Cursor cursor);

		/// <summary>
		///   Determines if the cursor position within the input is at a matching sequence.
		/// </summary>
		/// <param name="input">the input</param>
		/// <param name="cursor">the cursor</param>
		/// <returns>
		///   <em>true</em> if positioned at a matching sequence; otherwise <em>false</em>.
		/// </returns>
		public abstract bool At(string input, Cursor cursor);

		/// <summary>
		///   Tries to match the sequence at the cursor position, advancing the cursor if successful.
		/// </summary>
		/// <param name="input">the input</param>
		/// <param name="cursor">a reference to the cursor</param>
		/// <returns>
		///   <em>true</em> if the sequence matched at the initial cursor; otherwise <em>false</em>.
		/// </returns>
		public abstract bool TryAt(char[] input, ref Cursor cursor);

		/// <summary>
		///   Tries to match the sequence at the cursor position, advancing the cursor if successful.
		/// </summary>
		/// <param name="input">the input</param>
		/// <param name="cursor">a reference to the cursor</param>
		/// <returns>
		///   <em>true</em> if the sequence matched at the initial cursor; otherwise <em>false</em>.
		/// </returns>
		public abstract bool TryAt(string input, ref Cursor cursor);

		/// <summary>
		///   Captures the matching sequence at the cursor and advances the cursor.
		/// </summary>
		/// <param name="input">the input</param>
		/// <param name="cursor">a reference to the cursor</param>
		/// <returns>
		///   A <see cref="FlitBit.Parse.Capture" /> containing the sequence matched at the cursor.
		/// </returns>
		/// <exception cref="ParseException">thrown if the cursor is not positioned at a matching sequence.</exception>
		public virtual Capture Capture(char[] input, ref Cursor cursor)
		{
			var c = cursor;
			if (TryAt(input, ref cursor))
			{
				return new Capture(this, c, input.Slice(c, cursor.Offset - c));
			}
			throw new ParseException(c, this.ToString());
		}

		/// <summary>
		///   Captures the matching sequence at the cursor and advances the cursor.
		/// </summary>
		/// <param name="input">the input</param>
		/// <param name="cursor">a reference to the cursor</param>
		/// <returns>
		///   A <see cref="FlitBit.Parse.Capture" /> containing the sequence matched at the cursor.
		/// </returns>
		/// <exception cref="ParseException">thrown if the cursor is not positioned at a matching sequence.</exception>
		public virtual Capture Capture(string input, ref Cursor cursor)
		{
			var c = cursor;
			if (TryAt(input, ref cursor))
			{
				return new Capture(this, c, input.Substring(c, cursor.Offset - c));
			}
			throw new ParseException(c, this.ToString());
		}

		/// <summary>
		///   Gets an array of non-variable character matches at the front. The elements
		///   in the returned array are positional such that the item at index 0 represents
		///   an exact character match for the first input character, and so on.
		/// </summary>
		/// <returns></returns>
		public virtual CharMatch[] GetExactCharacterMatchesOnFront()
		{
			return new CharMatch[0];
		}

		/// <summary>
		///   Tries to capture a matching sequence at the cursor and advances the cursor if successful.
		/// </summary>
		/// <param name="input">the input</param>
		/// <param name="cursor">a reference to the cursor</param>
		/// <param name="value">The captured sequence if successful.</param>
		/// <returns>
		///   <em>true</em> if the sequence matched at the initial cursor; otherwise <em>false</em>.
		/// </returns>
		public virtual bool TryCapture(char[] input, ref Cursor cursor, out Capture value)
		{
			var c = cursor;
			if (TryAt(input, ref cursor))
			{
				value = new Capture(this, c, input.Slice(c, cursor.Offset - c));
				return true;
			}
			value = default(Capture);
			return false;
		}

		/// <summary>
		///   Tries to capture a matching sequence at the cursor and advances the cursor if successful.
		/// </summary>
		/// <param name="input">the input</param>
		/// <param name="cursor">a reference to the cursor</param>
		/// <param name="value">The captured sequence if successful.</param>
		/// <returns>
		///   <em>true</em> if the sequence matched at the initial cursor; otherwise <em>false</em>.
		/// </returns>
		public virtual bool TryCapture(string input, ref Cursor cursor, out Capture value)
		{
			var c = cursor;
			if (TryAt(input, ref cursor))
			{
				value = new Capture(this, c, input.Substring(c, cursor.Offset - c));
				return true;
			}
			value = default(Capture);
			return false;
		}

		/// <summary>
		///   Determines if the instance is equal to another.
		/// </summary>
		/// <param name="obj">the other object</param>
		/// <returns><em>true</em> if equal; otherwise <em>false</em>.</returns>
		public override bool Equals(object obj)
		{
			return obj is Match
				&& this.Equals((Match) obj);
		}

		/// <summary>
		///   Gets the object's hashcode.
		/// </summary>
		/// <returns></returns>
		public override int GetHashCode()
		{
			const int prime = 999067; // a random prime
			var result = CHashCodeSeed * prime;
			result ^= (int) _kind;
			return result;
		}

		/// <summary>
		///   Determines if the instance is equal to another.
		/// </summary>
		/// <param name="other">the other object</param>
		/// <returns></returns>
		public bool Equals(Match other)
		{
			return other != null
				&& _kind == other._kind;
		}
	}
}